#include "StdAfx.h"
#include "AnimationThread.h"
#include <string>
#include <MMSystem.h>

#pragma comment(lib,"winmm.lib")

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 1-millisecond target resolution
const int TARGET_RESOLUTION = 1;

// Number of frames with a delay of 0 ms before the
// animation thread yields to other running threads.
const int NO_DELAYS_PER_YIELD = 16;

const int MAX_STATS_INTERVAL = 1000000; // 1 second


LPCTSTR GetErrorString() 
{
	static TCHAR errMsg[MAX_PATH];

	// Retrieve the system error message for the last-error code
	LPVOID lpMsgBuf;
	DWORD dw = GetLastError();

	FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

	_stprintf_s( errMsg, _T("ERROR %d: %s"), dw, lpMsgBuf );
	
	LocalFree(lpMsgBuf);

	return errMsg;
}

DWORD CALLBACK AnimationThread::ThreadEntry(LPVOID pParam)
{
	AnimationThread* pThread = static_cast<AnimationThread*>(pParam);
	return pThread->Run();
}

inline DWORD GET_TIME()
{
	return timeGetTime() * 1000;
}

inline float TIME_TO_SECONDS(DWORD time)
{
	return (float)time / 1000000;
}

DWORD AnimationThread::Run()
{
	long beforeTime, afterTime, timeDiff, sleepTime;
	long overSleepTime = 0L;
	int noDelays = 0;
	long excess = 0L;

	m_dwThreadStartTime = GET_TIME();
	m_dwPrevStatsTime = m_dwThreadStartTime;

	beforeTime = m_dwThreadStartTime;

	m_bRunning = true;
	while (m_bRunning)
	{
		UpdateGame();	// game state is updated
		RenderGame();	// render to a buffer
		PaintScreen();	// draw buffer to screen

		afterTime = GET_TIME();
        timeDiff = afterTime - beforeTime;
        sleepTime = (m_FRAME_INTERVAL - timeDiff) - overSleepTime;

		if (sleepTime > 0) {
			// some time left in this cycle
			Sleep(sleepTime/1000);
			overSleepTime = (GET_TIME() - afterTime) - sleepTime;
        }
        else {
			// sleepTime <= 0; frame took longer than the period
			excess -= sleepTime;  // store excess time value
			overSleepTime = 0L;
			if (++noDelays >= NO_DELAYS_PER_YIELD) {
				Sleep(0); // give another thread a chance to run
				noDelays = 0;
			}
		}

		beforeTime = GET_TIME();

		// If frame animation is taking too long, update the game state
		// without rendering it, to get the updates/sec nearer to
		// the required FPS.
		int skips = 0;
		while((excess > m_FRAME_INTERVAL) && (skips < m_MAX_FRAME_SKIPS)) {
			excess -= m_FRAME_INTERVAL;
			UpdateGame();      // update state but don't render
			skips++;
		}

		m_dwFramesSkipped += skips;

		StoreStats();	
	}

	PrintStats();

	return 0;
}

void AnimationThread::StoreStats()
{
	m_dwFrameCount++;
	m_dwStatsInterval += m_FRAME_INTERVAL;

	if (m_dwStatsInterval >= MAX_STATS_INTERVAL)
	{
		DWORD timeNow = GET_TIME();
		m_dwTimeSpentInThread = timeNow - m_dwThreadStartTime;

		DWORD realElapsedTime = timeNow - m_dwPrevStatsTime; // time since last stats collection

        m_dwTotalElapsedTime += realElapsedTime;

        double timingError = ((double)realElapsedTime-m_dwStatsInterval) / m_dwStatsInterval * 100.0;

        m_dwTotalFramesSkipped += m_dwFramesSkipped;

		double actualFPS = 0; // calculate the latest FPS and UPS
		double actualUPS = 0;
		if (m_dwTotalElapsedTime > 0)
		{
			actualFPS = (double)m_dwFrameCount / TIME_TO_SECONDS(m_dwTotalElapsedTime);
			actualUPS = (double)(m_dwFrameCount + m_dwTotalFramesSkipped) / TIME_TO_SECONDS(m_dwTotalElapsedTime);
        }

		// store the latest FPS and UPS
        m_dFpsStore[ m_dwStatsCount % MAX_STATS_COUNT ] = actualFPS;
        m_dUpsStore[ m_dwStatsCount % MAX_STATS_COUNT ] = actualUPS;
        m_dwStatsCount = m_dwStatsCount + 1;

		double totalFPS = 0.0;     // total the stored FPSs and UPSs
		double totalUPS = 0.0;
		for (int i=0; i < MAX_STATS_COUNT; i++)
		{
			totalFPS += m_dFpsStore[i];
			totalUPS += m_dUpsStore[i];
		}

		if (m_dwStatsCount < MAX_STATS_COUNT)
		{ // obtain the average FPS and UPS
			m_dAverageFPS = totalFPS/m_dwStatsCount;
			m_dAverageUPS = totalUPS/m_dwStatsCount;
		}
		else
		{
			m_dAverageFPS = totalFPS/MAX_STATS_COUNT;
			m_dAverageUPS = totalUPS/MAX_STATS_COUNT;
		}

#ifdef SHOW_STATS
		TRACE("%8d %8d %10.2f%% %5dc %3d/%3d skip; %5.2f %5.2f afps; %5.2f %5.2f aups;\n",
			m_dwStatsInterval,
			realElapsedTime,
			timingError,
			m_dwFrameCount,
			m_dwFramesSkipped,
			m_dwTotalFramesSkipped,
			actualFPS,
			m_dAverageFPS,
			actualUPS,
			m_dAverageUPS );
#endif

		m_dwFramesSkipped = 0;
		m_dwPrevStatsTime = timeNow;
		m_dwStatsInterval = 0L;   // reset
	}
}

void AnimationThread::PrintStats()
{
	char text[128];
	sprintf_s( text, "Frame Count/Loss: %d / %d\r\n", m_dwFrameCount, m_dwTotalFramesSkipped );
	OutputDebugStringA(text);
	sprintf_s( text, "Average FPS: %f\n", m_dAverageFPS );
	OutputDebugStringA(text);
	sprintf_s( text, "Average UPS: %f\n", m_dAverageUPS );
	OutputDebugStringA(text);
	sprintf_s( text, "Time Spent: %10.6f secs\n", TIME_TO_SECONDS(m_dwTimeSpentInThread) );
	OutputDebugStringA(text);
}

AnimationThread::AnimationThread(int frameInterval,int maxFrameSkips)
{
	m_hAnimThread = NULL;
	m_dwThreadId = 0;

	m_FRAME_INTERVAL = frameInterval;
	m_MAX_FRAME_SKIPS = maxFrameSkips;

	m_bRunning = false;

	m_dwThreadStartTime = 0;
	m_dwTimeSpentInThread = 0;

	m_dwStatsInterval = 0;
	m_dwTotalElapsedTime = 0;
	m_dwFrameCount = 0;
	m_dwFramesSkipped = 0;
	m_dwTotalFramesSkipped = 0;
	m_dwStatsCount = 0;
	for ( int i = 0; i < MAX_STATS_COUNT; i++ )
	{
		m_dFpsStore[i] = 0;
		m_dUpsStore[i] = 0;
	}
	m_dAverageFPS = 0;
	m_dAverageUPS = 0;

}

AnimationThread::~AnimationThread(void)
{
}

void AnimationThread::Start()
{
	TIMECAPS tc;

	m_uTimerPeriod = 0;

	if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) == TIMERR_NOERROR) 
	{
		m_uTimerPeriod = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
		timeBeginPeriod(m_uTimerPeriod);
	}

	ASSERT(m_hAnimThread == NULL && m_bRunning == false);

	m_hAnimThread = CreateThread( NULL, 0, ThreadEntry, this, 0, &m_dwThreadId );

	ASSERT(m_hAnimThread);
	if ( !m_hAnimThread )
	{
		CString err = GetErrorString();
		TRACE( _T("Can't create thread, %s !!!\n"), err );
		return;
	}
}

void AnimationThread::Stop()
{
	m_bRunning = false;
	WaitForSingleObject( m_hAnimThread, INFINITE );
	BOOL b = CloseHandle(m_hAnimThread);
	if (!b)
	{
		CString err = GetErrorString();
		TRACE( _T("Can't close thread handle, %s !!!\n"), err );
	}
	m_hAnimThread = NULL;

	if ( m_uTimerPeriod != 0 )
	{
		MMRESULT res = timeEndPeriod(m_uTimerPeriod);
		ASSERT(res==TIMERR_NOERROR);
	}
}
